//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift.org project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift.org project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import JExtractSwiftLib
import Testing

@Suite
struct JNIEnumTests {
  let source = """
      public enum MyEnum {
        case first
        case second(String)
        case third(x: Int64, y: Int32)
      }
    """

  @Test
  func generatesJavaClass() throws {
    try assertOutput(
      input: source,
      .jni, .java,
      expectedChunks: [
        """
        // Generated by jextract-swift
        // Swift module: SwiftModule

        package com.example.swift;

        import org.swift.swiftkit.core.*;
        import org.swift.swiftkit.core.util.*;
        import java.util.*;
        import java.util.concurrent.atomic.AtomicBoolean;
        import org.swift.swiftkit.core.annotations.*;
        """,
        """
        public final class MyEnum implements JNISwiftInstance {
          static final String LIB_NAME = "SwiftModule";

          @SuppressWarnings("unused")
          private static final boolean INITIALIZED_LIBS = initializeLibs();
          static boolean initializeLibs() {
            System.loadLibrary(LIB_NAME);
            return true;
          }
        """,
        """
        private MyEnum(long selfPointer, SwiftArena swiftArena) {
          SwiftObjects.requireNonZero(selfPointer, "selfPointer");
          this.selfPointer = selfPointer;
        
          // Only register once we have fully initialized the object since this will need the object pointer.
          swiftArena.register(this);
        }
        """,
        """
        private final long selfPointer;
        """,
        """
        public long $memoryAddress() {
          return this.selfPointer;
        }
        """,
        """
        public static MyEnum wrapMemoryAddressUnsafe(long selfPointer, SwiftArena swiftArena) {
          return new MyEnum(selfPointer, swiftArena);
        }
        """,
        """
        private static native void $destroy(long selfPointer);
        """,
        """
        @Override
        public Runnable $createDestroyFunction() {
        long self$ = this.$memoryAddress();
        if (CallTraces.TRACE_DOWNCALLS) {
          CallTraces.traceDowncall("MyEnum.$createDestroyFunction",
              "this", this,
              "self", self$);
        }
        return new Runnable() {
          @Override
          public void run() {
            if (CallTraces.TRACE_DOWNCALLS) {
              CallTraces.traceDowncall("MyEnum.$destroy", "self", self$);
            }
            MyEnum.$destroy(self$);
          }
        };
        """
      ])
  }

  @Test
  func generatesDiscriminator_java() throws {
    try assertOutput(
      input: source,
      .jni, .java,
      detectChunkByInitialLines: 1,
      expectedChunks: [
        """
        public enum Discriminator {
          FIRST,
          SECOND,
          THIRD
        }
        """,
        """
        public Discriminator getDiscriminator() {
          return Discriminator.values()[$getDiscriminator(this.$memoryAddress())];
        }
        """,
        """
        private static native int $getDiscriminator(long self);
        """
      ])
  }

  @Test
  func generatesDiscriminator_swift() throws {
    try assertOutput(
      input: source,
      .jni, .swift,
      detectChunkByInitialLines: 1,
      expectedChunks: [
        """
        @_cdecl("Java_com_example_swift_MyEnum__00024getDiscriminator__J")
        func Java_com_example_swift_MyEnum__00024getDiscriminator__J(environment: UnsafeMutablePointer<JNIEnv?>!, thisClass: jclass, selfPointer: jlong) -> jint {
          ...
          switch (self$.pointee) {
            case .first: return 0
            case .second: return 1
            case .third: return 2
          }
        }
        """
      ])
  }

  @Test
  func generatesCases_java() throws {
    try assertOutput(
      input: source,
      .jni, .java,
      detectChunkByInitialLines: 1,
      expectedChunks: [
        """
        public sealed interface Case {}
        """,
        """
        public Case getCase() {
          Discriminator discriminator = this.getDiscriminator();
          switch (discriminator) {
            case FIRST: return this.getAsFirst().orElseThrow();
            case SECOND: return this.getAsSecond().orElseThrow();
            case THIRD: return this.getAsThird().orElseThrow();
          }
          throw new RuntimeException("Unknown discriminator value " + discriminator);
        }
        """,
        """
        public record First() implements Case {
          record $NativeParameters() {}
        }
        """,
        """
        public record Second(java.lang.String arg0) implements Case {
          record $NativeParameters(java.lang.String arg0) {}
        }
        """,
        """
        public record Third(long x, int y) implements Case {
          record $NativeParameters(long x, int y) {}
        }
        """
      ])
  }

  @Test
  func generatesCaseInitializers_java() throws {
    try assertOutput(
      input: source,
      .jni, .java,
      detectChunkByInitialLines: 1,
      expectedChunks: [
        """
        public static MyEnum first(SwiftArena swiftArena$) {
          return MyEnum.wrapMemoryAddressUnsafe(MyEnum.$first(), swiftArena$);
        }
        """,
        """
        public static MyEnum second(java.lang.String arg0, SwiftArena swiftArena$) {
          return MyEnum.wrapMemoryAddressUnsafe(MyEnum.$second(arg0), swiftArena$);
        }
        """,
        """
        public static MyEnum third(long x, int y, SwiftArena swiftArena$) {
          return MyEnum.wrapMemoryAddressUnsafe(MyEnum.$third(x, y), swiftArena$);
        }
        """
      ])
  }

  @Test
  func generatesCaseInitializers_swift() throws {
    try assertOutput(
      input: source,
      .jni, .swift,
      detectChunkByInitialLines: 1,
      expectedChunks: [
        """
        @_cdecl("Java_com_example_swift_MyEnum__00024first__")
        func Java_com_example_swift_MyEnum__00024first__(environment: UnsafeMutablePointer<JNIEnv?>!, thisClass: jclass) -> jlong {
          let result$ = UnsafeMutablePointer<MyEnum>.allocate(capacity: 1)
          result$.initialize(to: MyEnum.first)
          let resultBits$ = Int64(Int(bitPattern: result$))
          return resultBits$.getJNIValue(in: environment)
        }
        """,
        """
        @_cdecl("Java_com_example_swift_MyEnum__00024second__Ljava_lang_String_2")
        func Java_com_example_swift_MyEnum__00024second__Ljava_lang_String_2(environment: UnsafeMutablePointer<JNIEnv?>!, thisClass: jclass, arg0: jstring?) -> jlong {
          let result$ = UnsafeMutablePointer<MyEnum>.allocate(capacity: 1)
          result$.initialize(to: MyEnum.second(String(fromJNI: arg0, in: environment)))
          let resultBits$ = Int64(Int(bitPattern: result$))
          return resultBits$.getJNIValue(in: environment)
        }
        """,
        """
        @_cdecl("Java_com_example_swift_MyEnum__00024third__JI")
        func Java_com_example_swift_MyEnum__00024third__JI(environment: UnsafeMutablePointer<JNIEnv?>!, thisClass: jclass, x: jlong, y: jint) -> jlong {
          let result$ = UnsafeMutablePointer<MyEnum>.allocate(capacity: 1)
          result$.initialize(to: MyEnum.third(x: Int64(fromJNI: x, in: environment), y: Int32(fromJNI: y, in: environment)))
          let resultBits$ = Int64(Int(bitPattern: result$))
          return resultBits$.getJNIValue(in: environment)
        }
        """
      ])
  }

  @Test
  func generatesGetAsCase_java() throws {
    try assertOutput(
      input: source,
      .jni, .java,
      detectChunkByInitialLines: 1,
      expectedChunks: [
        """
        public Optional<First> getAsFirst() {
          if (getDiscriminator() != Discriminator.FIRST) {
            return Optional.empty();
          }
          return Optional.of(new First());
        }
        """,
        """
        public Optional<Second> getAsSecond() {
          if (getDiscriminator() != Discriminator.SECOND) {
            return Optional.empty();
          }
          Second.$NativeParameters $nativeParameters = MyEnum.$getAsSecond(this.$memoryAddress());
          return Optional.of(new Second($nativeParameters.arg0));
        }
        """,
        """
        public Optional<Third> getAsThird() {
          if (getDiscriminator() != Discriminator.THIRD) {
            return Optional.empty();
          }
          Third.$NativeParameters $nativeParameters = MyEnum.$getAsThird(this.$memoryAddress());
          return Optional.of(new Third($nativeParameters.x, $nativeParameters.y));
        }
        """
      ])
  }

  @Test
  func generatesGetAsCase_swift() throws {
    try assertOutput(
      input: source,
      .jni, .swift,
      detectChunkByInitialLines: 1,
      expectedChunks: [
        """
        @_cdecl("Java_com_example_swift_MyEnum__00024getAsSecond__J")
        func Java_com_example_swift_MyEnum__00024getAsSecond__J(environment: UnsafeMutablePointer<JNIEnv?>!, thisClass: jclass, self: jlong) -> jobject? {
          ...
          guard case .second(let _0) = self$.pointee else {
            fatalError("Expected enum case 'second', but was '\\(self$.pointee)'!")
          }
          let cache$ = _JNI_MyEnum.myEnumSecondCache
          let class$ = cache$.javaClass
          let method$ = _JNIMethodIDCache.Method(name: "<init>", signature: "(Ljava/lang/String;)V")
          let constructorID$ = cache$[method$]
          let newObjectArgs$: [jvalue] = [jvalue(l: _0.getJNIValue(in: environment) ?? nil)]
          return environment.interface.NewObjectA(environment, class$, constructorID$, newObjectArgs$)
        }
        """,
        """
        @_cdecl("Java_com_example_swift_MyEnum__00024getAsThird__J")
        func Java_com_example_swift_MyEnum__00024getAsThird__J(environment: UnsafeMutablePointer<JNIEnv?>!, thisClass: jclass, self: jlong) -> jobject? {
          ...
          guard case .third(let x, let y) = self$.pointee else {
            fatalError("Expected enum case 'third', but was '\\(self$.pointee)'!")
          }
          let cache$ = _JNI_MyEnum.myEnumThirdCache
          let class$ = cache$.javaClass
          let method$ = _JNIMethodIDCache.Method(name: "<init>", signature: "(JI)V")
          let constructorID$ = cache$[method$]
          let newObjectArgs$: [jvalue] = [jvalue(j: x.getJNIValue(in: environment)), jvalue(i: y.getJNIValue(in: environment))]
          return environment.interface.NewObjectA(environment, class$, constructorID$, newObjectArgs$)
        }
        """
      ])
  }
}
